package cloud.tianai.mate.captcha.validator.common.util;

import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: 天爱有情
 * @date 2023/1/3 19:09
 * @Description 轨迹工具包
 */
public class TrackUtils {


    /**
     * 轨迹特征提取
     *
     * @param imageCaptchaTrack
     * @return
     */
    public static List<Double> features(ImageCaptchaTrack imageCaptchaTrack) {
        return features(imageCaptchaTrack.getTrackList());
    }


    /**
     * 轨迹特征提取
     *
     * @param trackList
     * @return
     */
    public static List<Double> features(List<ImageCaptchaTrack.Track> trackList) {
        // 特征提取
        ImageCaptchaTrack.Track firstTrack = trackList.get(0);
        ImageCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1);
        // 轨迹点个数
        double trackCount = trackList.size();
        // x的最小值
        double xMin = firstTrack.getX();
        // y的最小值
        double yMin = firstTrack.getY();
        // y的最大值
        double yMax = yMin;
        // 总耗时
        double totalTime = lastTrack.getT();
        List<Float> xList = new ArrayList<>(trackList.size());
        List<Float> yList = new ArrayList<>(trackList.size());
        List<Float> tList = new ArrayList<>(trackList.size());
        Map<Float, Integer> xCountMap = new HashMap<>();
        Map<Float, Integer> yCountMap = new HashMap<>();
        Map<Float, Integer> tCountMap = new HashMap<>();
        double allX = 0;
        double allY = 0;
        // 回退次数
        int xBeforeBackNum = 0;
        int xAfterBackNum = 0;
        boolean xBackLock = false;
        double avgTime = totalTime / 2;
        int xAvgTimeCount = 0;
        int trackListCountAvg = trackList.size() / 2;
        List<Float> xDiffList = new ArrayList<>();
        List<Float> yDiffList = new ArrayList<>();
        List<Float> tDiffList = new ArrayList<>();
        for (int i = 0; i < trackList.size(); i++) {
            ImageCaptchaTrack.Track track = trackList.get(i);
            Float x = track.getX();
            Float y = track.getY();
            Float t = track.getT();
            if (xMin > x) {
                xMin = x;
            }
            if (yMin > y) {
                yMin = y;
            }
            if (yMax < y) {
                yMax = y;
            }
            xList.add(x);
            yList.add(y);
            tList.add(t);

            Integer xCurrentCount = yCountMap.getOrDefault(x, 0);
            xCountMap.put(y, xCurrentCount + 1);

            Integer yCurrentCount = yCountMap.getOrDefault(y, 0);
            yCountMap.put(y, yCurrentCount + 1);

            Integer tCurrentCount = yCountMap.getOrDefault(t, 0);
            tCountMap.put(t, tCurrentCount + 1);

            allX += x;
            allY += y;
            if (i != 0) {
                ImageCaptchaTrack.Track pre = trackList.get(i - 1);
                if (track.getX() < pre.getX()) {
                    if (!xBackLock) {
                        if (trackListCountAvg > i) {
                            xBeforeBackNum++;
                        } else {
                            xAfterBackNum++;
                        }
                    }
                    xBackLock = true;
                } else if (track.getX() > pre.getX()) {
                    xBackLock = false;
                }
                float xDiff = track.getX() - pre.getX();
                xDiffList.add(xDiff);
                float yDiff = track.getY() - pre.getY();
                yDiffList.add(yDiff);
                float tDiff = track.getT() - pre.getT();
                tDiffList.add(tDiff);

            }
            if (t < avgTime) {
                xAvgTimeCount++;
            }
        }
        // 取 50%
        int splitPos = (int) (trackCount * 0.75);
        ImageCaptchaTrack.Track splitPostTrack = trackList.get(splitPos < 1 ? 0 : splitPos - 1);
        ImageCaptchaTrack.Track stepOneFirstTrack = trackList.get(0);
        ImageCaptchaTrack.Track stepOneTwoTrack = trackList.get(splitPos);
        float posTime = splitPostTrack.getT() - stepOneFirstTrack.getT();
        double startAvgPosTime = posTime / (float) splitPos;
        double endAvgPosTime = (lastTrack.getT() - stepOneTwoTrack.getT()) / (float) (trackCount - splitPos);
        double tRatio = endAvgPosTime == 0 || startAvgPosTime == 0 ? 0 : endAvgPosTime / startAvgPosTime;
        double xStd = std(xList);
        double avgX = allX / trackCount;
        double yStd = std(yList);
        double avgY = allY / trackCount;
        double tStd = std(tList);
        // x,y,t 值相同的占比
        double xSameQuantityPercentage = getSameQuantityPercentage(xCountMap, trackCount);
        double ySameQuantityPercentage = getSameQuantityPercentage(yCountMap, trackCount);
        double tSameQuantityPercentage = getSameQuantityPercentage(tCountMap, trackCount);

        // 取总耗时一半的情况下，x滑动数量的比值
        double xAvgTimePercentage = xAvgTimeCount / trackCount;


        // x值前50%的滑动的距离, 和后50%滑动的距离的差值
        int xSplit = xList.size() / 2;
        double beforeStdX = std(xList.subList(0, xSplit));
        double endStdX = std(xList.subList(xSplit, xList.size()));
        double xPercentDiff = endStdX - beforeStdX;

        // 前百分之50的时间滑动的距离， 后百分之50的时间滑动的距离
        double timePercent50 = totalTime * 0.5;
        ImageCaptchaTrack.Track xMovePercent50 = trackList.stream().filter(t -> t.getT() > timePercent50).findFirst().get();
        float beforeTimePercentMoveX = xMovePercent50.getX() - trackList.get(0).getX();
        float endTimePercentMoveX = trackList.get(trackList.size() - 1).getX() - xMovePercent50.getX();
        double xPercentDiff2 = endTimePercentMoveX - beforeTimePercentMoveX;
        // 后百分之50的std - 前百分之50的y值的std，

        int ySplit = xList.size() / 2;
        List<Float> beforePercent50Y = yDiffList.subList(0, ySplit);
        List<Float> endPercent50Y = yDiffList.subList(ySplit, yDiffList.size());
        double beforeStdY = std(beforePercent50Y);
        double endStdY = std(endPercent50Y);
        double yPercentDiff = endStdY - beforeStdY;


        List<Double> features = new ArrayList<>(20);
        features.add(trackCount);
        features.add(xMin - firstTrack.getX());
        features.add(yMin - firstTrack.getY());
        features.add(yMax - firstTrack.getY());
        features.add(totalTime);
        features.add((double) xBeforeBackNum);
        features.add((double) xAfterBackNum);
        features.add(tRatio);
        features.add(xStd);
        features.add(avgX);
        features.add(yStd);
        features.add(avgY);
        features.add(tStd);
        features.add(xSameQuantityPercentage);
        features.add(ySameQuantityPercentage);
        features.add(tSameQuantityPercentage);
        features.add(xAvgTimePercentage);
        features.add(std(xDiffList));
        features.add(std(yDiffList));
        features.add(std(tDiffList));
        features.add(xPercentDiff);
        features.add(xPercentDiff2);
        features.add(yPercentDiff);
        features.add(startAvgPosTime);
        features.add(endAvgPosTime);
        return features;
    }

    private static double getSameQuantityPercentage(Map<Float, Integer> countMap, double trackCount) {
        Collection<Integer> values = countMap.values();
        List<Integer> sort = values.stream().sorted(Comparator.comparingInt(a -> (int) a).reversed()).collect(Collectors.toList());
        Integer maxCount = sort.get(sort.size() - 1);
        return maxCount / trackCount;
    }


    public static double max(List<Float> list) {
        Optional<Float> max = list.stream().max(Comparator.comparing(v -> v));
        return max.get();
    }

    public static double std(List<Float> list) {
        int m = list.size();
        double sum = 0;
        //求和
        for (int i = 0; i < m; i++) {
            sum += list.get(i);
        }
        //求平均值
        double dAve = sum / m;
        double dVar = 0;
        //求方差
        for (int i = 0; i < m; i++) {
            double pow = Math.pow(list.get(i) - dAve, 2);
            if (!isNaNOrInfinite(pow)) {
                dVar += pow;
            } else {
                dVar += list.get(i) - dAve;
            }
        }
        return Math.sqrt(dVar / m);
    }

    public static final boolean isNaNOrInfinite(final double v) {
        return Double.isNaN(v) || (v == Double.POSITIVE_INFINITY)
                || (v == Double.NEGATIVE_INFINITY);
    }

}
